/*
 * Copyright 2011 The Rusted Roof, LLC.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.proteusframework.platformservice.persistence.packaging;

import org.proteusframework.core.api.model.INamespace;
import org.proteusframework.core.base.Namespace;
import org.proteusframework.core.util.Assert;
import org.proteusframework.platformservice.persistence.api.IPackagingVisitor;
import org.proteusframework.platformservice.persistence.api.IProjectDescriptor;
import org.proteusframework.platformservice.persistence.api.IProjectManager;
import org.proteusframework.platformservice.persistence.api.IProjectManagerVisitor;
import org.proteusframework.platformservice.persistence.project.ProjectDescriptor;

import java.io.*;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

final class ProjectManagerZipSerializer extends ZipSerializer implements IProjectManagerVisitor
{
    private static final Logger logger = Logger.getLogger(ProjectManagerZipSerializer.class.getCanonicalName());

    public ProjectManagerZipSerializer(ZipOutputStream zipOutputStream)
    {
        super(zipOutputStream);
    }

    ProjectManagerZipSerializer(ZipFile zipFile)
    {
        super(zipFile);
    }

    @Override
    public void visit(IProjectManager projectManager) throws IOException
    {
        Assert.parameterNotNull(projectManager, "Parameter 'projectManager' must not be null");

        if (getDirection() == IPackagingVisitor.DIRECTION_OUT)
        {
            if (!projectManager.isProjectOpen())
            {
                throw new IllegalStateException("Cannot export a project when no project is open");
            }

            IProjectDescriptor projectDescriptor = projectManager.getCurrentProject();

            try
            {
                ZipEntry ze = new ZipEntry(PROJECT_DESCRIPTOR_ENTRY);
                ze.setComment((null == projectDescriptor.getDescription())
                        ? Namespace.NULL_TOKEN
                        : projectDescriptor.getDescription());

                //
                // We always want to write to our own internal BAOS to ensure a full record write is possible, otherwise
                // we might end up with a corrupted serialization record!
                //
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                DataOutputStream dos = new DataOutputStream(baos);

                dos.writeInt(SerializationHeader.ProjectDescriptor.ordinal());

                dos.writeInt(SerializationHeader.Namespace.ordinal());
                dos.writeUTF(Namespace.toCanonical(projectDescriptor, true));

                dos.writeInt(SerializationHeader.DisplayableName.ordinal());

                dos.writeUTF(projectDescriptor.getName());
                dos.writeUTF((null == projectDescriptor.getDescription())
                        ? Namespace.NULL_TOKEN
                        : projectDescriptor.getDescription());

                dos.writeInt(SerializationHeader.PropertyGroups.ordinal());
                dos.writeInt(projectDescriptor.listPropertyGroups().size());


                for (String groupKey : projectDescriptor.listPropertyGroups())
                {
                    dos.writeInt(SerializationHeader.PropertyGroup.ordinal());
                    dos.writeUTF(groupKey);

                    List<String> groupKeys = projectDescriptor.listPropertyGroupKeys(groupKey);

                    dos.writeInt(SerializationHeader.Properties.ordinal());
                    dos.writeInt(groupKeys.size());
                    for (String propertyName : groupKeys)
                    {
                        dos.writeUTF(propertyName);
                        dos.writeUTF(projectDescriptor.getProperty(groupKey, propertyName));
                    }
                }

                try
                {
                    byte[] data = baos.toByteArray();
                    ze.setSize(data.length);
                    zipStream.putNextEntry(ze);
                    zipStream.write(data, 0, data.length);
                    zipStream.closeEntry();

                    logger.fine("Wrote zip entry " + ze.getName());

                    //
                    // OK, now we know we wrote the record successfully - increment our count
                    //
                    incrementRecordCount();
                } catch (Exception e)
                {
                    logger.warning("Unable to serialize project descriptor " + projectDescriptor.toString() + ": " + e.getMessage());
                }

            } catch (IOException e)
            {
                e.printStackTrace();
            }
        } else
        {

            Enumeration<? extends ZipEntry> zipFiles = this.zipFile.entries();

            ZipEntry projectDescriptorZipEntry = null;
            while (zipFiles.hasMoreElements())
            {
                ZipEntry entry = zipFiles.nextElement();

                if (entry.getName().equals(IProjectManagerVisitor.PROJECT_DESCRIPTOR_ENTRY))
                {
                    projectDescriptorZipEntry = entry;
                    break;
                }
            }

            if (null == projectDescriptorZipEntry)
            {
                throw new IOException("Invalid package file; no project descriptor entry found");
            }

            //
            // Read the project descriptor and open up the project
            InputStream is = zipFile.getInputStream(projectDescriptorZipEntry);

            DataInputStream dis = new DataInputStream(is);

            try
            {
                String currentGroupKey = null;

                //noinspection InfiniteLoopStatement
                do
                {
                    int headerID = dis.readInt();

                    SerializationHeader header = SerializationHeader.values()[headerID];

                    switch (header)
                    {
                        case ProjectDescriptor:
                            break;
                        case Namespace:
                            INamespace projectNamespace = Namespace.fromCanonical(dis.readUTF());
                            IProjectDescriptor projectDescriptor = new ProjectDescriptor(projectNamespace);
                            projectManager.create(projectDescriptor);
                            break;
                        case DisplayableName:
                            // Ignore the name, as our default implementation always uses INamespace
                            dis.readUTF();

                            String description = dis.readUTF();
                            String projectDescription = (description.equals(Namespace.NULL_TOKEN) ? null : description);
                            projectManager.setCurrentProjectDescription(projectDescription);
                            break;
                        case PropertyGroups:
                            // Ignore the group count
                            dis.readInt();
                            break;
                        case PropertyGroup:
                            currentGroupKey = dis.readUTF();
                            break;
                        case Properties:
                            String propertyKey = dis.readUTF();
                            String propertyValue = dis.readUTF();
                            projectManager.setCurrentProjectProperty(currentGroupKey, propertyKey, propertyValue);
                            break;
                    }
                } while (true);

            } catch (EOFException ignore)
            {

            } catch (IOException e)
            {
                e.printStackTrace();
            } catch (org.proteusframework.platformservice.persistence.project.ProjectInitializationException e)
            {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }

            IProjectDescriptor curDescriptor = projectManager.getCurrentProject();
            logger.info("New project created from package: " + curDescriptor);
        }
    }
}
